#include <stdio.h>
#include "dri/KEYBOARD_Driver.h"
#include "dri/MOTOR_Driver.h"
#include "dri/AUDIO_Driver.h"
#include "dri/LED_Driver.h"
#include "dri/FINGER_Driver.h"
#include <string.h>
#include <unistd.h>
#include "BT_Driver.h"
#include "freertos/idf_additions.h"
#include "WIFI_Driver.h"
#include "PASSWORD_Driver.h"
#include "dri/OTA_Driver.h"
#include "dri/MQTT_Driver.h"

#include "cJSON.h"
volatile uint8_t ota_trigger_flag = 0; // OTA触发标志
char ota_fw_url[128] = {0};

// 创建队列
static QueueHandle_t gpio_event_queue = NULL;
// 输入次数
static uint8_t password_count = 0;
// 保存输入的按键值
static uint8_t password_array[6] = {0};

// 是否要录入指纹 0表示不录入指纹，1表示录入指纹
static uint8_t is_enroll_flag = 0;
// 是否要升级固件 0表示不升级固件，1表示升级固件
static uint8_t is_ota_flag = 0;

// 是否有手指放在指纹模块上面 0表示没有手指，1表示有手指
static uint8_t is_finger_on = 0;

/// 当回调函数执行时，参数 arg 是产生中断的 GPIO 引脚号。
/// `IRAM_ATTR`宏定义表示回调函数在内存的某个特定位置
// 中断 服务函数必须要短小，中断中如果代码过多，延时多会卡死，这里就是将isr处理函数和rtos任务分离开来，再用rtos任务
//处理isr任务的原因
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
    /// arg中保存了触发中断的GPIO引脚号
    uint32_t gpio_num = (uint32_t)arg;

    // 当由手指按在指纹模块上时，要么是验证指纹，要么是录入指纹
    // 都需要禁用所有外部中断
    if (gpio_num == FINGER_TOUCH_INT)
    {
        gpio_isr_handler_remove(FINGER_TOUCH_INT);// 移除中断服务函数
        gpio_isr_handler_remove(KEYBOARD_INT);
        is_finger_on = 1;
    }

    /// 将 GPIO 引脚号添加到 gpio_event_queue 队列中。
    // 在将gpio_num发送到队列时，需要屏蔽中断，队列是将数据和指针放入的，这个放入的引脚本质上也是数字
    xQueueSendFromISR(gpio_event_queue, &gpio_num, NULL);
    // 为什么不直接在中断处理函数中读取按键值呢？因为如果直接在中断函数中读取按键值，
    // 会导致中断函数执行时间过长，影响系统的实时性。
}

/// 录入指纹的rtos任务
static void enroll_fingerprint_task(void *arg)
{
    while (1)
    {
        // 当进入录入指纹流程时
        // 连续按下3次M键，则置为1
        if (is_enroll_flag)
        {
            AUDIO_Play(80); // 请放置手指语音
            // 轮询直到有手指按在指纹模块上
            while (!is_finger_on)
            {
                DelayMs(10);
            }
            // 获取已经存储的模板数量
            uint8_t templates_count = FINGER_GetTemplatesNumber();
            FINGER_Enroll(templates_count + 1);

            // 清扫工作
            is_enroll_flag = 0;
            is_finger_on = 0;
            FINGER_Cancel();
            while (FINGER_Sleep() == 1)
                ;//while为真就会一直空循环。为假才能跳出来执行后面的

            gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler,
                                 (void *)KEYBOARD_INT);
            gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler,
                                 (void *)FINGER_TOUCH_INT);
        }
        DelayMs(200);
    }
}

// 验证指纹的rtos任务
/// 验证指纹的rtos任务
/// 识别指纹的rtos任务
static void identify_fingerprint_task(void *arg)
{
    for (;;)
    {
        if (!is_enroll_flag && is_finger_on) // 都是1正常运行，不录入指纹，有手指按在指纹模块上
        {
            if (FINGER_Identify() == 0)
            {
                printf("指纹识别成功.\r\n");
                AUDIO_Play(85);
                MOTOR_OpenLock();
            }
            else
            {
                AUDIO_Play(86);
                printf("指纹识别失败.\r\n");
            }
            is_finger_on = 0;
            // 指纹模块先睡眠，然后再将指纹中断添加回来。
            FINGER_Cancel();
            while (FINGER_Sleep() == 1)
                ;
            // 这里是之前的中断函数判断是否有手指按在指纹模块上，将中断函数给删除了，这里给添加回来
            gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler,
                                 (void *)KEYBOARD_INT);
            gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler,
                                 (void *)FINGER_TOUCH_INT);
        }
        DelayMs(100);
    }
}

// 处理gpio中断的rtos任务
static void process_isr(void *arg)
{
    uint32_t gpio_num2;
    for (;;) // 无限循环
    {
        /// 如果队列中有 GPIO 中断事件，将 GPIO 引脚号存储到 gpio_num 变量中。
        //引脚号从队列gpio_event_queue中取出
        if (xQueueReceive(gpio_event_queue, &gpio_num2, portMAX_DELAY))
        {
            /// 如果产生中断的GPIO引脚号是 GPIO_NUM_0，也就是键盘中断引脚。
            if (gpio_num2 == KEYBOARD_INT)
            {
                /// 读取按键值。
                uint8_t key_num = KEYBOARD_ReadKey();
                /// 打印到上位机。通过串口助手查看。
                printf("press key: %d\r\n", key_num);
                LED_Light(key_num);

                password_array[password_count++] = key_num;

                // 连续按下3次M，进入录指纹流程
                if (password_count >= 3 && password_array[password_count - 1] == 'M' &&
                    password_array[password_count - 2] == 'M' &&
                    password_array[password_count - 3] == 'M')
                {
                    password_count = 0;
                    memset(password_array, 0, 6);
                    is_enroll_flag = 1;
                }

                // 连续按下3次#，进入ota流程
                if (password_count >= 3 && password_array[password_count - 1] == '#' &&
                    password_array[password_count - 2] == '#' &&
                    password_array[password_count - 3] == '#')
                {
                    password_count = 0;
                    memset(password_array, 0, 6);
                    is_ota_flag = 1;
                }

                // 和flash 中存储的密码进行比较
                if (password_count == 6)
                {
                    nvs_handle password_handle;
                    // 打开命名空间，模式NVS_READWRITE，指向password_handle句柄，将按键数据存入句柄中，密码存在esp32的NVS中，可以在设备重启后保留数据
                    // NVS是ESP32 flash中的一个存储分区，用于存储键值对数据。每个键值对由一个键名和一个值组成，
                    // 键名是一个字符串，值可以是任意类型的数据。NVS是一个轻量级的存储系统，用于存储设备的配置信息。
                    nvs_open("namespace", NVS_READWRITE, &password_handle);
                    size_t length = 7;//size_t可以自动适应数据长度
                    char password[7] = {0};
                    char password_in_flash[7] = {0};
                    // 从flash中读取密码  将数据存入password_in_flash中
                    nvs_get_str(password_handle, "password", password_in_flash, &length);
                    nvs_close(password_handle);
                    // 将输入的密码和flash中的密码进行比较.如果是按键6个内就对不上，会进入i<6中验证是不是临时密码
                    int i = 0;
                    for (i = 0; i < 6; i++)
                    {
                        // 密码存储是ascii码,当通过键盘输入是字符，但是我们通过函数赋予了对于的数字，
                        // 当两者进行比较就需要从flash获取的密码减去‘0’，这样就可以将字符转换为数字
                        // 通过-0的方式将字符转换为数字,password_array是输入的按键值，
                        if (password_array[i] != password_in_flash[i] - '0')
                            break;
                    }
                    if (i == 6)
                    {
                        MOTOR_OpenLock();
                    }
                    if (i < 6)
                    {
                        // 校验是否为临时密码
                        uint32_t pwd = password_array[0] * 100000 +
                                       password_array[1] * 10000 +
                                       password_array[2] * 1000 + password_array[3] * 100 +
                                       password_array[4] * 10 + password_array[5];
                        if (PASSWORD_ValidateTempPassword(pwd))
                        {
                            MOTOR_OpenLock();
                        }
                    }
                    password_count = 0;
                    memset(password_array, 0, 6);
                }
            }
        }
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

// rtos任务，用来进行ota升级
void ota_rtos_task(void *args)
{
    while (1)
    {
        if (ota_trigger_flag || is_ota_flag)
        { // 兼容物理按键和远程触发
            ESP_LOGI("OTA", "Starting OTA from %s", ota_fw_url);

            // 执行OTA升级
            if (strlen(ota_fw_url) > 0)
            {
                OTA_Start(ota_fw_url); // 修改OTA驱动接收URL参数
            }
            else
            {
                ESP_LOGE("OTA", "No firmware URL provided");
            }

            // 复位标志
            ota_trigger_flag = 0;
            is_ota_flag = 0;
            memset(ota_fw_url, 0, sizeof(ota_fw_url));
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main(void)
{
    KEYBOARD_Init();
    MOTOR_Init();
    AUDIO_Init();
    LED_Init();
    FINGER_Init();

    FINGER_GetSerialNumber();
    FINGER_GetTemplatesNumber();
    FINGER_SetSecurityLevelAsZero();
    FINGER_Cancel();
    while (FINGER_Sleep() == 1) // 睡眠函数为真就会循环，停住
        ;

    // Initialize NVS
    // 初始化esp32自带的flash，每次开机都要初始化，为了给指纹，密码这种数据提供存储，
    // 但是必须要有对应的模块有直接代码写出将数据存入nvs，在当前的代码中nvs主要存储的是本地密码，
    // 本地密码是在蓝牙模块中，通过蓝牙设置后才存在的，如果蓝牙中没有设置密码，那么nvs中就没有数据
    // ESP_ERR_NVS_NO_FREE_PAGES（NVS空间满了，无空闲页）
    // 或者ESP_ERR_NVS_NEW_VERSION_FOUND（NVS版本发生改变，需要格式化）这两种错误才调用 
    esp_err_t ret;
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    BT_Init();   // 蓝牙发送openlock，开锁；发送chgpwd+，修改密码
    WIFI_Init(); // wifi连接
    // while (1)
    // {//测试led工作状态
    //     for (int i = 0; i < 12; i++)
    //     {
    //         LED_Light(i);
    //     }
    //     DelayMs(1000);
    // }

    MQTT_Init();

    /// 创建一个队列，用来保存中断事件。
    gpio_event_queue = xQueueCreate(10, sizeof(uint32_t));

    /// 启用中断
    gpio_install_isr_service(0);
    /// 来自 GPIO_NUM_0 也就是 KEYBOARD_INT 的中断触发的回调函数是
    /// gpio_isr_handler 。 将KEYBOARD_INT的中断处理逻辑代理给gpio_isr_handler
    /// 当KEYBOARD_INT产生中断，将触发gpio_isr_handler的执行
    //将键盘uart，指纹iic的中断统一起来放入gpio的框架中，方便处理
    gpio_isr_handler_add(KEYBOARD_INT, gpio_isr_handler, (void *)KEYBOARD_INT); // 给对应的引脚添加中断处理函数，gpio_isr_handler是中断发生时自动调用的回调函数，KEYBOARD_INT是传递给回调函数的参数，表示哪个引脚发生了中断
    //中断服务函数执行完快速退出执行rtos的任务
    gpio_isr_handler_add(FINGER_TOUCH_INT, gpio_isr_handler,
                         (void *)FINGER_TOUCH_INT); // 针对指纹模块的引脚注册中断处理回调函数

    // 将process_isr注册为一个rtos任务，这里将rtos任务都创建成同一个优先级，就会按照时间的顺序依次运行
    xTaskCreate(process_isr, "process_isr", 2048, NULL, 10, NULL);//gpio的中断任务
    // 将enroll_fingerprint_task注册为一个rtos任务
    xTaskCreate(enroll_fingerprint_task, "enroll_fingerprint_task", 2048, NULL, 10, NULL);//录入指纹
    xTaskCreate(identify_fingerprint_task, "identify_fingerprint_task", 2048, NULL, 10, NULL);//校验指纹
    xTaskCreate(ota_rtos_task, "ota", 8192, NULL, 10, NULL);//ora升级
}